﻿#region NeoPatterns
// 
// NeoPatterns framework is released under LGPLv3 license.
// See http://www.opensource.org/licenses/lgpl-3.0.html for more details.
// 
// Please visit http://neopatterns.wikidot.com for more information about NeoPatterns
// 
// Copyright (c) Pascal Craponne 2008
// 
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using NeoPatterns.Core.Aop;
using NeoPatterns.Core.Aop.Implementation;
using NeoPatterns.Exception;

namespace NeoPatterns.Core.Registry.Implementation
{
    /// <summary>
    /// ObjectRegistry is NeoPatterns main class. It holds object definitions and singletons
    /// </summary>
    internal class ObjectRegistry : IObjectRegistry
    {
        /// <summary>
        /// The parent provides all objects, interceptors, and so on.
        /// </summary>
        protected ObjectRegistry parentObjectRegistry;

        private readonly object lockObject = new object();

        /// <summary>
        /// ObjectDefinitions lists all definitions by name.
        /// </summary>
        private readonly IDictionary<string, ObjectDefinition> objectDefinitions = new Dictionary<string, ObjectDefinition>();

        /// <summary>
        /// Returns an object definition by name
        /// </summary>
        /// <param name="objectName"></param>
        /// <returns></returns>
        internal virtual ObjectDefinition GetObjectDefinition(string objectName)
        {
            ObjectDefinition objectDefinition;
            lock (lockObject)
                objectDefinitions.TryGetValue(objectName, out objectDefinition);
            // if the object can't be found in local scope, then search in parent
            if (objectDefinition == null && parentObjectRegistry != null)
                objectDefinition = parentObjectRegistry.GetObjectDefinition(objectName);
            return objectDefinition;
        }

        /// <summary>
        /// Tracks all object definitions by Type (all base types and interfaces are referenced here)
        /// </summary>
        private readonly IDictionary<Type, IList<ObjectDefinition>> objectDefinitionsByType = new Dictionary<Type, IList<ObjectDefinition>>();

        /// <summary>
        /// Returns object definitions related to a type
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        internal virtual IList<ObjectDefinition> GetObjectDefinitionsByType(Type type)
        {
            // first, get definitions for this ObjectRegistry
            IList<ObjectDefinition> currentObjectDefinitionsByType;
            lock (lockObject)
            {
                if (objectDefinitionsByType.ContainsKey(type))
                    currentObjectDefinitionsByType = objectDefinitionsByType[type];
                else
                    currentObjectDefinitionsByType = new ObjectDefinition[0];
            }
            if (parentObjectRegistry == null)
                return currentObjectDefinitionsByType;

            // then, if present, ask the parent, and merge definitions
            var totalObjectDefinitionsByType = new List<ObjectDefinition>(parentObjectRegistry.GetObjectDefinitionsByType(type));
            totalObjectDefinitionsByType.AddRange(currentObjectDefinitionsByType);
            return totalObjectDefinitionsByType;
        }

        /// <summary>
        /// Returns object definitions related to a type, with a valid list (to fill it internally)
        /// Caution: the lockObject must be locked outside the call
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private IList<ObjectDefinition> GetObjectDefinitionsByTypeList(Type type)
        {
            lock (lockObject)
            {
                IList<ObjectDefinition> list;
                if (!objectDefinitionsByType.TryGetValue(type, out list))
                {
                    list = new List<ObjectDefinition>();
                    objectDefinitionsByType[type] = list;
                }
                return list;
            }
        }

        /// <summary>
        /// Tracks all interceptors
        /// </summary>
        private readonly IList<AspectDefinition> aspectDefinitions = new List<AspectDefinition>();

        /// <summary>
        /// Wrapper to interceptor definitions
        /// </summary>
        internal virtual IEnumerable<AspectDefinition> AspectDefinitions
        {
            get
            {
                // first, see our interceptors
                lock (lockObject)
                {
                    foreach (var aspectDefinition in aspectDefinitions)
                    {
                        yield return aspectDefinition;
                    }
                }
                // then parent's interceptors
                if (parentObjectRegistry != null)
                {
                    foreach (var aspectDefinition in parentObjectRegistry.AspectDefinitions)
                    {
                        yield return aspectDefinition;
                    }
                }
            }
        }

        /// <summary>
        /// Singletons have the same lifetime as this ObjectRegistry
        /// </summary>
        private readonly IDictionary<string, object> Singletons = new Dictionary<string, object>();

        /// <summary>
        /// Returns a singleton given its name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private object GetSingleton(string name)
        {
            object singleton;
            lock (lockObject)
                Singletons.TryGetValue(name, out singleton);

            if (singleton == null && parentObjectRegistry != null)
                singleton = parentObjectRegistry.GetSingleton(name);

            return singleton;
        }

        public IAspectBuilder AspectBuilder { get; set; }

        /// <summary>
        /// Protected ctor, to create child ObjectRegistry
        /// </summary>
        /// <param name="parent"></param>
        protected ObjectRegistry(ObjectRegistry parent)
        {
            parentObjectRegistry = parent;
            AspectBuilder = parent.AspectBuilder;
        }

        public ObjectRegistry()
        {
            AspectBuilder = new InheritanceAspectBuilder();
        }

        /// <summary>
        /// Returns all base types for a given Type
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        protected virtual IList<Type> GetBaseTypes(Type type)
        {
            var baseTypes = new List<Type>();
            if (type.IsInterface)
                baseTypes.Add(type);
            else
            {
                for (var t = type; t != null; t = t.BaseType)
                    baseTypes.Add(t);
            }
            return baseTypes;
        }

        /// <summary>
        /// Returns all base types and interfaces for a given type
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        protected virtual IList<Type> GetBaseTypesAndInterfaces(Type type)
        {
            var baseTypes = GetBaseTypes(type);
            foreach (var implementedInterface in type.GetInterfaces())
                baseTypes.Add(implementedInterface);
            return baseTypes;
        }

        /// <summary>
        /// Register an object definition
        /// </summary>
        /// <param name="objectName">Object unique name</param>
        /// <param name="type">Object type</param>
        /// <param name="advisors">Specific advisors (to this type)</param>
        /// <param name="instance">Instance: true for creation at each call, false for singleton</param>
        /// <param name="creator">Creation delegate (object creator(IObjectRegistry objectRegistry))</param>
        /// <param name="initializer">Initialization delegate (void initializer(object o,IObjectRegistry objectRegistry)</param>
        public virtual void RegisterDefinition(string objectName, Type type, IList<Ref> advisors, bool instance, Delegate creator, Delegate initializer)
        {
            lock (lockObject)
            {
                var userObject = new ObjectDefinition(objectName, type, advisors, instance, creator, initializer);

                // checks if an object with the same name was not registered previously
                if (GetObjectDefinition(userObject.Name) != null)
                    throw new DuplicateNameException(userObject.Name);

                // then register the object
                objectDefinitions[userObject.Name] = userObject;

                // and 
                var baseTypesAndInterfaces = GetBaseTypesAndInterfaces(userObject.Type);
                foreach (var baseType in baseTypesAndInterfaces)
                    GetObjectDefinitionsByTypeList(baseType).Add(userObject);
            }
        }

        /// <summary>
        /// Registers an object as interceptor
        /// </summary>
        /// <param name="pointcutSelectorRef"></param>
        /// <param name="advisorRef"></param>
        public virtual void RegisterAspect(Ref pointcutSelectorRef, Ref advisorRef)
        {
            lock (lockObject)
            {
                aspectDefinitions.Add(new AspectDefinition(pointcutSelectorRef, advisorRef));
            }
        }

        /// <summary>
        /// Internal creation stack. Used to detect deadlocks
        /// </summary>
        private static readonly IList<string> creation = new List<string>();

        /// <summary>
        /// Object getter. May create and initialize object.
        /// Creation and Initialization are separated to help used avoiding deadlocks
        /// </summary>
        /// <param name="objectName"></param>
        /// <returns></returns>
        public virtual object Get(string objectName)
        {
            lock (lockObject)
            {
                // 1. If it is an already present singleton, use it
                var instance = GetSingleton(objectName);
                if (instance != null)
                    return instance;

                // 2.1. otherwise create it
                var objectDefinition = GetObjectDefinition(objectName);

                instance = PrepareInstance(objectDefinition);

                // 4. if singleton, keep it
                if (!objectDefinition.Instance)
                    Singletons[objectName] = instance;

                // 5. then initialize it
                if (objectDefinition.Initialize != null)
                    objectDefinition.Initialize.DynamicInvoke(instance, this);

                return instance;
            }
        }

        /// <summary>
        /// Initializes an object with dependencies
        /// This method is useful when objects are already created (forms, web pages) 
        ///         and just need to be injected
        /// </summary>
        /// <param name="instance"></param>
        /// <param name="objectType"></param>
        public virtual void Initialize(object instance, Type objectType)
        {
            // consistency check: the request type must be assignable
            if (!objectType.IsAssignableFrom(instance.GetType()))
                throw new ConsistencyException();

            // first, find the names
            var names = GetNamesForType(objectType);
            // for more than one definition, we can not handle
            if (names.Count > 1)
                throw new TypeAmbiguityException();
            // if we have one definition, inject the initialization
            if (names.Count == 1)
            {
                var objectName = names[0];
                var objectDefinition = GetObjectDefinition(objectName);

                if (objectDefinition.Initialize != null)
                    objectDefinition.Initialize.DynamicInvoke(instance, this);
            }
        }

        private object PrepareInstance(ObjectDefinition userObject)
        {
            var objectName = userObject.Name;
            try
            {
                // 2.2. record what we're creation
                if (creation.Contains(objectName))
                    throw new CircularCreationException(objectName);

                creation.Add(objectName);

                // 2.4. add interceptors, if needed
                var aspects = GetAspects(userObject).ToList();

                // if we add an interceptor or a dynamic target ability, 
                // then we create a dynamic proxy for the instance
                if (aspects.Count > 0)
                    return AspectBuilder.CreateInstance(userObject.Type, aspects);
                else
                    return CreateInstance(userObject);
            }
            finally
            {
                // 2.5. forget what we've created
                creation.Remove(objectName);
            }
        }

        private IEnumerable<Aspect> GetAspects(ObjectDefinition userObject)
        {
            // common aspects (real ones)
            foreach (var aspectDefinition in aspectDefinitions)
            {
                // this is to avoid loops
                if (aspectDefinition.PointcutSelectorRef.Name == userObject.Name)
                    continue;
                if (aspectDefinition.AdvisorRef.Name == userObject.Name)
                    continue;

                // then get objects
                var pointcutSelector = (IPointcut)aspectDefinition.PointcutSelectorRef.GetObject(this);
                if (pointcutSelector.Select(userObject.Type, userObject.Name))
                    yield return new Aspect(pointcutSelector, (IAdvisor)aspectDefinition.AdvisorRef.GetObject(this));
            }
            // specific aspects
            if (userObject.Advisors == null)
                yield break;
            foreach (var advisor in userObject.Advisors)
            {
                yield return new Aspect(null, (IAdvisor)advisor.GetObject());
            }
        }

        private object CreateInstance(ObjectDefinition userObject)
        {
            object instance;
            // 2.3 create instance
            if (userObject.Create != null)
                instance = userObject.Create.DynamicInvoke(this);
            else
                instance = Create(userObject.Type);
            return instance;
        }

        /// <summary>
        /// Object getter, generic version
        /// </summary>
        /// <typeparam name="T">Request type</typeparam>
        /// <param name="objectName">Object name</param>
        /// <returns></returns>
        public virtual T Get<T>(string objectName)
        {
            return (T)Get(objectName);
        }

        /// <summary>
        /// Creates an objet, given its type
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        protected virtual object Create(Type type)
        {
            try
            {
                return Activator.CreateInstance(type);
            }
            catch (MissingMethodException e)
            {
                throw new MissingDefaultConstructorException(string.Empty, e);
            }
        }

        /// <summary>
        /// Returns all object names matching a given type
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public virtual IList<string> GetNamesForType(Type type)
        {
            return (from userObject in GetObjectDefinitionsByType(type) select userObject.Name).ToArray();
        }

        /// <summary>
        /// Returns all object names matching a given type, generic version
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public virtual IList<string> GetNamesForType<T>()
        {
            return GetNamesForType(typeof(T));
        }

        //protected virtual Type GetUniqueInterface(Type classType)
        //{
        //    var interfacesLeft = GetUniqueInterfaces(classType);

        //    if (interfacesLeft.Count != 1)
        //        return null;

        //    return interfacesLeft[0];
        //}

        //protected virtual IList<Type> GetUniqueInterfaces(Type classType)
        //{
        //    if (!classType.IsClass)
        //        throw new ConsistencyException(classType.FullName);

        //    var interfaces = classType.GetInterfaces();
        //    var interfacesLeft = new List<Type>(interfaces);
        //    foreach (var interface_ in interfaces)
        //    {
        //        foreach (var baseInterface in interface_.GetInterfaces())
        //        {
        //            interfacesLeft.Remove(baseInterface);
        //        }
        //    }
        //    return interfacesLeft;
        //}

        /// <summary>
        /// Creates a child IObjectRegistry, inheriting objects from parent.
        /// </summary>
        /// <returns></returns>
        public IObjectRegistry CreateChild()
        {
            return new ObjectRegistry(this);
        }
    }
}